package com.atguigu.daijia.common.annotation;

import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component  //交给spring
@Aspect  //标注该类是一个切面类
@RequiredArgsConstructor
public class NeedLoginAspect {
    private final RedisTemplate redisTemplate;
    private static final Logger log = LoggerFactory.getLogger(NeedLoginAspect.class);

    //环绕通知，登录判断  切入点表达式：指定对哪些规则的方法进行增强
    //1、execution(): 表达式主体，可以扫描控制层的接口、某个注解、或者其他需要扫描的类。
    //2、第一个*号：表示返回类型，*号表示所有的类型，比如public,protect,private等。
    //3、包名：表示需要拦截的包名，后面的两个句点表示当前包和当前包的所有子包，com.atguigu.daijia.*.controller.*.*包、子孙包下所有类的方法。
    //4、第二个*号：表示子包名，*号表示所有子包。
    //5、第三个*号：表示类名，*号表示所有子包下的类。
    //6、*(..):最后这个星号表示方法名，*号表示所有的方法，后面括弧里面表示方法的参数，两个句点表示任何参数。
    //7. @annotation() 标识处理被标注的注解，括号是方法中注解参数名字 是在下面的方法里参数传递过来， 参数类型 才是真正的注解名，参数名 是 @annotation(）括号里要填的
    //@Before 前置通知：在某连接点（JoinPoint）之前执行的通知， 但这个通知不能阻止连接点前的执行。
    //@After 后通知：当某连接点退出的时候执行的通知 （不论是正常返回还是异常退出）。
    //@AfterReturning 返回后通知 ：在某连接点正常完成后执行的通知， 不包括抛出异常的情况。
    //@Around 环绕通知 ：包围一个连接点的通知，类似Web中Servlet ,规范中的Filter的doFilter方法。可以在方法的调用前后完成 自定义的行为，也可以选择不执行。
    //@AfterThrowing 抛出异常后通知：在方法抛出异常退出时执行的通知。
    @Around("execution(* com.atguigu.daijia.*.controller.*.*(..)) && @annotation(needLogin)")
    public Object login(ProceedingJoinPoint proceedingJoinPoint, NeedLogin needLogin) throws Throwable {
//        log.info(">>这个接口需要登录，开始通过AOP进行登录验证");
//        log.info("1.通过RequestContextHolder 获取 request对象");
        //注意： 由于切面不是controller无法直接得到request对象，这里使用spring提供的RequestContextHolder工具类获取request对象
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sra.getRequest();
//        log.info("2.拿到请求头的token");
        String token = request.getHeader("token");
        if(!StringUtils.hasText(token)){
            log.info("2.1 请求头里没有token，说明没有登录，抛异常 停止执行，提示前端未登录");
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        String customerId = (String)redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
//        log.info("3.从redis中获取用户id=>{}",customerId);
        if(!StringUtils.hasText(customerId)){
            log.info("3.1 token过期，抛异常 停止执行，提示前端登录过期");
            throw new GuiguException(ResultCodeEnum.SIGN_OVERDUE);
        }
//        log.info("4.将缓存拿到的登录userId 放入到ThreadLocal，方便整个线程执行过程中使用");
        AuthContextHolder.setUserId(Long.parseLong(customerId));
//        log.info("5.执行业务方法");
        return proceedingJoinPoint.proceed();
    }

}
